package global

import (
	"database/sql"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"git.oschina.net/YPTArch/basic/models"
	"git.oschina.net/YPTArch/km_api_dispatcher/sys/logs"
	_ "github.com/go-sql-driver/mysql"
)

type mysql struct {
	db *sql.DB
}

func newMySQL(user string, password string, host string, port int, database string, charset string, connectionLimit int) (m *mysql) {
	m = &mysql{}
	m.db, _ = sql.Open("mysql",
		""+user+":"+password+"@tcp("+
			host+":"+strconv.Itoa(port)+
			")/"+
			database+"?charset="+charset+"")
	m.db.SetMaxOpenConns(connectionLimit)
	m.db.SetMaxIdleConns(connectionLimit / 2)
	m.db.Ping()
	return
}

func (m *mysql) Query(c cmd, params map[string]interface{}) (record []map[string]interface{}, err error) {
	sql, err := applyParamToSQL(c, params)
	if err != nil {
		logs.Logger.Error("applyParamToSQL(c, params) -> " + err.Error())
		return nil, err
	}
	// Log.Info(c.Type2 + "." + c.Type3 + " (" + c.Name + ") -> " + sql)
	rows, err := m.db.Query(sql)
	if err != nil {
		logs.Logger.Error("m.db.Query(sql) -> " + err.Error())
		logs.Logger.Error("SQL : " + sql)
		return nil, err
	}
	defer rows.Close()

	columns, err := rows.Columns()
	if err != nil {
		logs.Logger.Error("rows.Columns() -> " + err.Error())
		return nil, err
	}
	scanArgs := make([]interface{}, len(columns))
	values := make([]interface{}, len(columns))
	for i := range values {
		scanArgs[i] = &values[i]
	}
	for rows.Next() {
		//将行数据保存到record字典
		err = rows.Scan(scanArgs...)
		record = append(record, make(map[string]interface{}))
		for i, val := range values {
			if val != nil {
				record[len(record)-1][columns[i]] = string(val.([]byte))
			} else {
				record[len(record)-1][columns[i]] = ""
			}
		}
	}
	// Log.Info("Query -> " + strconv.Itoa(len(record)))
	return record, nil
}

func queryInTran(tran *sql.Tx, c cmd, params map[string]interface{}) (record []map[string]interface{}, err error) {
	sql, err := applyParamToSQL(c, params)
	if err != nil {
		logs.Logger.Error("applyParamToSQL(c, params) -> " + err.Error())
		return nil, err
	}
	// Log.Info(c.Type2 + "." + c.Type3 + " (" + c.Name + ") -> " + sql)
	rows, err := tran.Query(sql)
	if err != nil {
		logs.Logger.Error("m.db.Query(sql) -> " + err.Error())
		logs.Logger.Error("SQL : " + sql)
		return nil, err
	}
	defer rows.Close()

	columns, err := rows.Columns()
	if err != nil {
		logs.Logger.Error("rows.Columns() -> " + err.Error())
		return nil, err
	}
	scanArgs := make([]interface{}, len(columns))
	values := make([]interface{}, len(columns))
	for i := range values {
		scanArgs[i] = &values[i]
	}
	for rows.Next() {
		//将行数据保存到record字典
		err = rows.Scan(scanArgs...)
		record = append(record, make(map[string]interface{}))
		for i, val := range values {
			if val != nil {
				record[len(record)-1][columns[i]] = string(val.([]byte))
			} else {
				record[len(record)-1][columns[i]] = ""
			}
		}
	}
	// Log.Info("Query -> " + strconv.Itoa(len(record)))
	return record, nil
}

func (m *mysql) Execute(c cmd, params map[string]interface{}) (result map[string]interface{}, err error) {
	result = make(map[string]interface{})
	sql, err := applyParamToSQL(c, params)
	if err != nil {
		logs.Logger.Error("applyParamToSQL(c, params) -> " + err.Error())
		return
	}
	r, err := m.db.Exec(sql)
	if err != nil {
		logs.Logger.Error("m.db.Exec(sql) -> " + err.Error())
		logs.Logger.Error("SQL : " + sql)
		return
	}
	rowsAffected, _ := r.RowsAffected()
	result["rows_affected"] = rowsAffected
	result["state"] = 0
	result["msg"] = strconv.FormatInt(rowsAffected, 10) + "条记录受影响"
	return
}

func executeInTran(tran *sql.Tx, c cmd, params map[string]interface{}) (result map[string]interface{}, err error) {
	result = make(map[string]interface{})
	sql, err := applyParamToSQL(c, params)
	if err != nil {
		logs.Logger.Error("applyParamToSQL(c, params) -> " + err.Error())
		return
	}
	r, err := tran.Exec(sql)
	if err != nil {
		logs.Logger.Error("m.db.Exec(sql) -> " + err.Error())
		logs.Logger.Error("SQL : " + sql)
		return
	}
	rowsAffected, _ := r.RowsAffected()
	result["rows_affected"] = rowsAffected
	result["state"] = 0
	result["msg"] = strconv.FormatInt(rowsAffected, 10) + "条记录受影响"
	return
}

//BeginTrans ...
func (m *mysql) BeginTrans(cmdList []*cmd, params map[string]interface{}) models.Result {
	tran, err := m.db.Begin()
	if err != nil {
		logs.Logger.Error("BeginTrans Error -> " + err.Error())
		return models.Result{Code: 500, Msg: "服务器内部错误", Data: "API处理异常"}
	}
	commit := true
	defer func() {
		if commit {
			// Log.Info("Commit")
			tran.Commit()
		} else {
			// Log.Info("Rollback")
			tran.Rollback()
		}
	}()

	result := make(map[string]interface{})
	for _, cmd := range cmdList {
		allowContinue := false //允许继续的状态
		switch cmd.Type2 {
		case "call":
			//存储过程，需要判断执行状态，要求每一个存储过程必须SELECT state
			queryResult, err := queryInTran(tran, *cmd, params)
			if err != nil {
				commit = false
				logs.Logger.Error("Query Error -> " + err.Error())
				return models.Result{Code: 500, Msg: "服务器内部错误", Data: err.Error()}
			}
			if len(queryResult) <= 0 {
				commit = false
				logs.Logger.Error("存储过程无输出值")
				return models.Result{Code: 500, Msg: "服务器内部错误", Data: "存储过程无输出值"}
			}
			if _, existed := queryResult[0]["state"]; !existed {
				commit = false
				logs.Logger.Error("存储过程无输出状态值")
				return models.Result{Code: 500, Msg: "服务器内部错误", Data: "存储过程无输出状态值"}
			}
			result[cmd.Name] = queryResult[0]
			params[cmd.Name] = queryResult[0]
			allowContinue = len(cmd.ContinueStateList) == 0
			if len(cmd.ContinueStateList) > 0 {
				for _, continueState := range cmd.ContinueStateList {
					state, err := strconv.ParseInt(queryResult[0]["state"].(string), 10, 64)
					if err != nil {
						commit = false
						logs.Logger.Error("Query Error -> " + err.Error())
						return models.Result{Code: 500, Msg: "服务器内部错误", Data: err.Error()}
					}
					if continueState == state {
						allowContinue = true
						break
					}
				}
			} else {
				state, err := strconv.ParseInt(queryResult[0]["state"].(string), 10, 64)
				if err != nil {
					commit = false
					logs.Logger.Error("Query Error -> " + err.Error())
					return models.Result{Code: 500, Msg: "服务器内部错误", Data: err.Error()}
				}
				allowContinue = state == 0
			}
		case "insert":
			fallthrough
		case "update":
			fallthrough
		case "delete":
			//存储过程，需要判断执行状态，要求每一个存储过程必须SELECT state
			allowContinue = true //允许继续
			execResult, err := executeInTran(tran, *cmd, params)
			if err != nil {
				commit = false
				logs.Logger.Error("Execute Error -> " + err.Error())
				return models.Result{Code: 500, Msg: "服务器内部错误", Data: err.Error()}
			}
			result[cmd.Name] = execResult
			break
		case "select":
			//存储过程，需要判断执行状态，要求每一个存储过程必须SELECT state
			allowContinue = true //允许继续
			queryResult, err := m.Query(*cmd, params)
			if err != nil {
				commit = false
				logs.Logger.Error("Query Error -> " + err.Error())
				return models.Result{Code: 500, Msg: "服务器内部错误", Data: err.Error()}
			}
			switch cmd.Type3 {
			case "multi":
				result[cmd.Name] = queryResult
				params[cmd.Name] = queryResult
				if len(queryResult) == 0 {
					result[cmd.Name] = make([]string, 0) //避免转json出现null
				}
			case "single":
				if len(queryResult) == 0 {
					commit = false
					logs.Logger.Error("select.single未找到任何一条记录")
					return models.Result{Code: 3454, Msg: "未找到任何一条记录"}
				}
				result[cmd.Name] = queryResult[0]
				params[cmd.Name] = queryResult[0]
			default:
				commit = false
				logs.Logger.Error("不支持命令类型->" + cmd.Type2 + "." + cmd.Type3)
				return models.Result{Code: 500, Msg: "服务器内部错误", Data: "不支持命令类型->" + cmd.Type2 + "." + cmd.Type3}
			}
		default:
			commit = false
			logs.Logger.Error("不支持命令类型->" + cmd.Type2)
			return models.Result{Code: 500, Msg: "服务器内部错误", Data: "不支持命令类型->" + cmd.Type2}
		}
		// if !allowContinue {
		// 	//状态值不符合继续执行的条件
		// 	commit = false
		// 	Log.Warn("命令[" + cmd.Name + "]状态不符合继续执行的条件")
		// 	log.Println(123)
		// 	return models.Result{Code:
		// 		, Msg: result[cmd.Name].(map[string]interface{})["msg"].(string), Data: result}
		// }
		if !allowContinue {
		}

	}
	return models.Result{Code: 200, Msg: "", Data: result}
}

//获取参数值
func getParamValue(params map[string]interface{}, p *param) (interface{}, bool) {
	pNameSplit := strings.Split(p.Name, ".")
	existed := true
	var currentParam interface{}
	currentParam = params
	for _, pNameSplitItem := range pNameSplit {
		switch currentParam.(type) {
		case map[string]interface{}:
			if p.Type == "in.arr" {
				currentParam, existed = currentParam.(map[string]interface{})[pNameSplitItem+"[]"]
			} else {
				currentParam, existed = currentParam.(map[string]interface{})[pNameSplitItem]
			}
			if !existed {
				break
			}
		default:
			existed = false
		}
	}
	return currentParam, existed
}

// 将参数应用到sql语句
func applyParamToSQL(c cmd, params map[string]interface{}) (string, error) {
	replaceStr := func(sourceStr string, replaceStartIndex int, replaceLen int, replaceValue interface{}) string {
		strStart := sourceStr[0:replaceStartIndex]
		strEnd := sourceStr[replaceStartIndex+replaceLen : len(sourceStr)]
		newStr := fmt.Sprintf("%s%s%s", strStart, replaceValue, strEnd)
		return newStr
	}

	paramValues := make(map[string]interface{})
	for _, p := range c.ParamList {
		paramValue, existed := getParamValue(params, p)
		if !existed {
			return "", errors.New("缺失参数：" + p.Name + "")
		}
		if p.Type == "in.arr" {
			paramValues[p.Name] = paramValue
		} else {
			switch paramValue.(type) {
			case string:
				paramValues[p.Name] = paramValue
			case []string:
				paramValues[p.Name] = paramValue.([]string)[0]
			}
		}
	}

	sqlStr := c.Template
	for i := 0; i < len(sqlStr); i++ {
		if string(sqlStr[i]) == "@" {
			pn := ""
			var pv interface{}
			var pv2 string

			for j := i + 1; j < len(sqlStr); j++ {
				pn += string(sqlStr[j])
				existed := false
				pv, existed = paramValues[pn]
				if existed {
					switch pv.(type) {
					case string:
						// if (params[paramName] == undefined) throw new Error(`缺失参数：${paramName}`);
						// let paramValue = params[paramName].toString();
						//由于要替换为SQL语句单引号中的字符，所以单引号需要转义  'xxx' -> ''xxx''
						pv2 = strings.Replace(pv.(string), "'", "''", -1)
						// pv = strings.Replace(pv.(string), "'", "''", -1)
					case []string:
						// if (params[paramName] == undefined) throw new Error(`缺失参数：${paramName}`);
						// let paramValue = params[paramName].toString();
						//由于要替换为SQL语句单引号中的字符，所以单引号需要转义  'xxx' -> ''xxx''
						for _, pvItem := range pv.([]string) {
							pv2 += "'" + strings.Replace(pvItem, "'", "''", -1) + "'" + ","
							// pv.([]string)[i] = "'" + strings.Replace(pvItem, "'", "''", -1) + "'"
						}
						pv2 = pv2[:len(pv2)-1]
						fmt.Println()
						// pv = strings.Join(pv.([]string), ",")
					}
					defined := false
					for _, definedParam := range c.ParamList {
						if definedParam.Name == pn {
							defined = true
							break
						}
					}
					if !defined {
						pn = ""
					}
					break
				} else if string(sqlStr[j]) == "@" {
					pn = ""
					i = j
					break
				}
				if j == len(sqlStr)-1 && !existed {
					pn = ""
				}
			}
			if pn != "" {
				sqlStr = replaceStr(sqlStr, i, len(pn)+1, pv2)
				i += len(fmt.Sprintf("%s", pv2))
			}
		}
	}
	logs.Logger.Info("CMD: " + sqlStr)
	return sqlStr, nil
}
